/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package flowvis;

import flowsimulation.FlowSimulator;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCanvas;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

/**
 * @TODO: Put links to the GL procedures which are used here.
 * @author michel
 * @author waldo (assignment 2): change of deprecated methods.
 *
 * Visualization handles the OpenGL rendering context of the application.
 * It also deals with mouse dragging and window resizing. The update() method
 * calls the simulator to compute the next time step, and generates a repaint()
 * event which causes the visualization to redraw the screen.
 */
public class Visualization implements GLEventListener {

    GLU glu = new GLU();
    List<Renderer> renderers = Collections.synchronizedList(new ArrayList<Renderer>());  
    //Deprecated: Vector<Renderer> renderers = new Vector<Renderer>();
    GLCanvas canvas;
    FlowSimulator simulator;
    int winWidth, winHeight;
    int lmx = 0, lmy = 0;	//remembers last mouse location

    /**
     * Extension of MouseMetionAdapter, to catch the drags from the mouse in AWT (Java).
     */
    private class MouseListener extends MouseMotionAdapter {
       
        /**
         * Manages the drag of mouse in AWT.
         * @param e , mouse event in AWT.
         */
        @Override public void mouseDragged(MouseEvent e) {
            drag(e.getX(), e.getY());
        }

    }

    /**
     * Constructor. Sets canvas and adds mouse motion listener to it.
     * @param canvas 
     */
    public Visualization(GLCanvas canvas) {
        this.canvas = canvas;
        // listens to drag events
        canvas.addMouseMotionListener(new MouseListener());
    }

    /**
     * Sets the flow simulator.
     * @param simulator 
     */
    public void setSimulator(FlowSimulator simulator) { this.simulator = simulator; }
    /**
     * Add a new renderer (i.e. visualization method) to the visualization
     * @param vis , renderer implementation.
     */
    public void addRenderer(Renderer vis) { renderers.add(vis); }
    //public void addRenderer(Renderer vis) { renderers.addElement(vis); }
    /**
     * Perform a simulation step and update the screen
     */
    public void update() {
        simulator.do_one_simulation_step();
        canvas.repaint(50);
    }

    /**
     * When the user drags with the mouse, add a force that corresponds to the direction of the mouse
     * cursor movement. Also inject some new matter into the field at the mouse location.
     * @param mx , detected movement of the mouse in x axis.
     * @param my , detected movement of the mouse in y axis.
     */
    void drag(int mx, int my) {
        int xi, yi, X, Y;
        double dx, dy, len;
        int DIM = simulator.getGridSize();
        // Compute the array index that corresponds to the cursor location
        xi = (int) ((double) (DIM + 1) * ((double) mx / (double) winWidth));
        yi = (int) ((double) (DIM + 1) * ((double) (winHeight - my) / (double) winHeight));

        X = xi;
        Y = yi;
        if (X > (DIM - 1)) X = DIM - 1;
        if (Y > (DIM - 1)) Y = DIM - 1;
        if (X < 0) X = 0;
        if (Y < 0) Y = 0;
        // Add force at the cursor location
        my = winHeight - my;
        dx = mx - lmx;
        dy = my - lmy;
        len = Math.sqrt(dx * dx + dy * dy);
        if (len != 0.0) {
            dx *= 0.1 / len;
            dy *= 0.1 / len;
        }
        simulator.addForce(X, Y, dx, dy);
        simulator.setDensity(X, Y, 10.0d);
        lmx = mx;
        lmy = my;
    }

   /**
    * (1) Perform one simulation step.<br>
    * (2) Get the OpenGL rendering context.<br>
    * (3) Clear screen and set the view transform to the identity matrix.<br>
    * (4) Call all available renderer.<br>
    * @param drawable 
    */
   @Override public void display(GLAutoDrawable drawable) {
        // perform one simulation step
        update();
        // get the OpenGL rendering context
        GL gl = drawable.getGL();
        // clear screen and set the view transform to the identity matrix
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        // call the visualize() methods of all subscribed renderers
        for (int i = 0; i < renderers.size(); i++) {
            //Deprecated: renderers.elementAt(i).visualize(gl);
            renderers.get(i).visualize(gl);
            // blocking call ensures drawing of renderer is completed before
            // next one starts
            gl.glFlush();
        }
    }

   /**
    * Reshape handles window's resize.
    * @param drawable
    * @param x
    * @param y
    * @param width
    * @param height 
    */
   @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
        GL gl = drawable.getGL();
        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluOrtho2D(0.0, (double) width, 0.0, (double) height);
        for (int i = 0; i < renderers.size(); i++) {
            //Deprecated: renderers.elementAt(i).setWinWidth(width);
            renderers.get(i).setWinWidth(width);
            //Deprecated: renderers.elementAt(i).setWinHeight(height);
            renderers.get(i).setWinHeight(height);
        }
        winWidth = width;
        winHeight = height;
        canvas.setMinimumSize(new Dimension(0,0));
    }

    /**
     * Empty implementation (it is not needed).
     * @param drawable 
     */
   @Override public void init(GLAutoDrawable drawable){ }
   /**
    * Empty implementation (it is not needed).
    * @param drawable
    * @param modeChanged
    * @param deviceChanged 
    */
   @Override public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { }

}
